#include "Board.h"
#include "lib_AT91SAM7X256Lite.h"
#include "SSD1306Spi.h"

/*Global Variable definition*/
//http://fabienroyer.wordpress.com/2011/01/16/driving-an-adafruit-ssd1306-oled-display-with-a-netduino/
//http://code.google.com/p/sistemasembebidos/source/browse/?r=6#svn%2Ftrunk%2Ffirmware%2FFreeRTOS%2FDemo%2FARM7_AT91SAM7S64_KEIL%2FModules%2Fspi
//Netduino helper : http://netduinohelpers.codeplex.com/
//Very simple SPI code, http://code.google.com/p/skybot-quadrotor/source/browse/trunk/SAM7%20Sources/at91sam7s-ek/SkyBot/s_spi.c?r=9
tSpiPort spiPort0;
tSpiPort spiPort1;

eSpiInitError SSD1306_SPI_Initialize(eCSDevice device)
{
	unsigned portLONG ulPeriphA = ((unsigned portLONG) AT91C_PA16_MISO | (unsigned portLONG) AT91C_PA17_MOSI | (unsigned portLONG) AT91C_PA18_SPCK);
	unsigned portLONG ulPeriphB = (unsigned portLONG) 0;        
 
	/*Enable SPI0 PIO (SI, SO, SCK, CS)*/
	switch (device)
	{
		case ChipSelect0: ulPeriphA |= SPI0_IO_CS0A; ulPeriphB |= SPI0_IO_CS0B; break;
		case ChipSelect1: ulPeriphA |= SPI0_IO_CS1A; ulPeriphB |= SPI0_IO_CS1B; break;
		case ChipSelect2: ulPeriphA |= SPI0_IO_CS2A; ulPeriphB |= SPI0_IO_CS2B; break;
		case ChipSelect3: ulPeriphA |= SPI0_IO_CS3A; ulPeriphB |= SPI0_IO_CS3B; break;
		default: return spiInitMaxCs;
	}
	AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, ulPeriphA, ulPeriphB);

	/*Enable SPI0 Clocks*/
	AT91F_PMC_EnablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_SPI0);

	/*Disable SPI0 while configuring*/
	AT91F_SPI_Disable(AT91C_BASE_SPI0);

	/*Disable PDC for SPI0*/
	AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI0);
	AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI0);

	/*Initialize PDC TX and RX*/
	AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI0, 0, 0);
	AT91F_PDC_SetNextTx(AT91C_BASE_PDC_SPI0, 0, 0);
	AT91F_PDC_SetRx(AT91C_BASE_PDC_SPI0, 0, 0);
	AT91F_PDC_SetNextRx(AT91C_BASE_PDC_SPI0, 0, 0);
 
	/*Enable PDC for SPI0*/
	AT91F_PDC_EnableRx(AT91C_BASE_PDC_SPI0);
	AT91F_PDC_EnableTx(AT91C_BASE_PDC_SPI0);

	/*Reset the module, necessarily 2 for the SPI0 state machine*/
	AT91F_SPI_Reset(AT91C_BASE_SPI0); AT91F_SPI_Reset(AT91C_BASE_SPI0);

	/*Enable SPI0 module*/
	AT91F_SPI_Enable(AT91C_BASE_SPI0);

	/*Configure Master mode comunication with Variable Peripheral Select and Chip select w/o decode*/
	AT91F_SPI_CfgMode(AT91C_BASE_SPI0, AT91C_SPI_MSTR | AT91C_SPI_MODFDIS | AT91C_SPI_PS_FIXED);

	/*Configure and enable interrupts*/
	AT91F_SPI_DisableIt(AT91C_BASE_SPI0, AT91C_SPI_TXEMPTY | AT91C_SPI_RXBUFF);
	
	//From at91lib\peripherals\irq\aic.c,
	//http://www.keil.com/dd/docs/arm/atmel/sam7s/at91sam7s64.h
	//AT91C_AIC_SRCTYPE_INT_LEVEL_SENSITIVE is AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL
	
	AT91F_AIC_ConfigureIt(/*AT91C_BASE_AIC,*/ AT91C_ID_SPI0, AT91C_AIC_PRIOR_LOWEST + 1,
		/*AT91C_AIC_SRCTYPE_INT_LEVEL_SENSITIVE,*/AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, ( void (*)(void) ) vSpiISREntry0);
	AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_SPI0);
 
	/*Create and initialize semaphores*/
	if (!spiPort0.dev_1_Enabled && !spiPort0.dev_2_Enabled && !spiPort0.dev_3_Enabled && !spiPort0.dev_4_Enabled)
	{
		vSemaphoreCreateBinary(spiPort0.mutex.xSemaphore);
		vSemaphoreCreateBinary(spiPort0.event.xSemaphore);
		spiPort0.mutex.xBlockTime = INITIAL_OS_BLOCK_TIME_MAX;
	}
 
	/*Mark module as enabled, store config data*/
	switch (device)
	{
		case ChipSelect0: spiPort0.dev_1_Enabled = 1; break;
		case ChipSelect1: spiPort0.dev_2_Enabled = 1; break;
		case ChipSelect2: spiPort0.dev_3_Enabled = 1; break;
		case ChipSelect3: spiPort0.dev_4_Enabled = 1; break;
		default: return spiInitMaxCs;
	}

	return   spiInitSuccess;
}

void SSD1306_SPI_Close(eCSDevice device)
{
	unsigned portLONG ulPeriphA;
 
	/*Disable SPI0 peripherial pins*/
	switch (device)
	{
		case ChipSelect0: ulPeriphA = SPI0_IO_CS0A | SPI0_IO_CS0B; spiPort0.dev_1_Enabled = 0; break;
		case ChipSelect1: ulPeriphA = SPI0_IO_CS1A | SPI0_IO_CS1B; spiPort0.dev_2_Enabled = 0; break;
		case ChipSelect2: ulPeriphA = SPI0_IO_CS2A | SPI0_IO_CS2B; spiPort0.dev_3_Enabled = 0; break;
		case ChipSelect3: ulPeriphA = SPI0_IO_CS3A | SPI0_IO_CS3B; spiPort0.dev_4_Enabled = 0; break;
		default: return;
	}
 
	if (!spiPort0.dev_1_Enabled && !spiPort0.dev_2_Enabled && !spiPort0.dev_3_Enabled && !spiPort0.dev_4_Enabled)
	{
		ulPeriphA |= ((unsigned portLONG) AT91C_PA16_MISO | (unsigned portLONG) AT91C_PA17_MOSI | (unsigned portLONG) AT91C_PA18_SPCK);

		/*Disable PDC for SPI0*/
		AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI0);
		AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI0);    

		/*Disable SPI0 module*/
		AT91F_SPI_Disable(AT91C_BASE_SPI0);

		/*Disable SPI0 Clocks*/
		AT91F_PMC_DisablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_SPI0);

		/*Disable interrupts*/
		AT91F_SPI_DisableIt(AT91C_BASE_SPI0, AT91C_SPI_TXEMPTY | AT91C_SPI_RXBUFF);
		AT91F_AIC_DisableIt(AT91C_BASE_AIC, AT91C_ID_SPI0);

		/*Release Semaphores*/
		vQueueDelete(spiPort0.mutex.xSemaphore);
		vQueueDelete(spiPort0.event.xSemaphore);
	}
 
	AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, ulPeriphA);
}

unsigned portCHAR SSD1306_SPI_ReadWrite(pSpiRWdata pData)
{
	unsigned portCHAR errCode;  
	unsigned portCHAR divider = pData->clockDivider;
	unsigned portCHAR bHoldCS = 0;
 
	switch (pData->device)
	{
		case ChipSelect0: if (!spiPort0.dev_1_Enabled) {return ERR_DEV_UNINIT;}; break;
		case ChipSelect1: if (!spiPort0.dev_2_Enabled) {return ERR_DEV_UNINIT;}; break;
		case ChipSelect2: if (!spiPort0.dev_3_Enabled) {return ERR_DEV_UNINIT;}; break;
		case ChipSelect3: if (!spiPort0.dev_4_Enabled) {return ERR_DEV_UNINIT;}; break;
		default: return ERR_OUT_OF_BOUNDS;
	}
 
	if (xSemaphoreTake(spiPort0.mutex.xSemaphore, (portTickType) spiPort0.mutex.xBlockTime ) != pdTRUE)
	{
		return ERR_SEM_TIMEOUT;
	}
 
	/*Set baud rate for SPI0*/
	if (divider < SPI_MIN_DIVIDER) {divider = SPI_MIN_DIVIDER;}
	if (divider > 1) {divider &= 0xFE;}
 
	/*Configure for the selected Chip Select, the clock phase, bits per transfer, baud rate and
	 if Chip Select Active After Transfer
	*/
	if (pData->holdCS)
		{ bHoldCS = AT91C_SPI_CSAAT; }
	AT91F_SPI_CfgCs(AT91C_BASE_SPI0, pData->device, AT91C_SPI_CPOL | AT91C_SPI_BITS_8 | ((unsigned portLONG) divider << 8) | ((unsigned portLONG) bHoldCS));

	/*Disable PDC while configuring or on the fly*/
	#if SPI_SENDRCV_DIS_W_SETTING == 1
	AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI);
	AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI);
	#endif
 
	/*Take blocking semaphore*/
	xSemaphoreTake(spiPort0.event.xSemaphore, 0);

	/*Load wait block time*/
	spiPort0.event.xBlockTime = pData->xBlockTime;

	/*Has bytes to write*/
	if (pData->sizeW == 0)
	{
		AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI0, (char *) pData->dataR, pData->sizeR);
	}
	else
	{
		AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI0, (char *) pData->dataW, pData->sizeW);
	}

	/*Has bytes to read*/
	if (pData->sizeR > 0)
	{
		AT91F_PDC_SetRx(AT91C_BASE_PDC_SPI0, (char *) pData->dataR, pData->sizeR);
		/*When TX & Rx only RX interrupt is necessary*/
		AT91F_SPI_EnableIt(AT91C_BASE_SPI0, AT91C_SPI_RXBUFF);

		#if SPI_SENDRCV_DIS_W_SETTING == 1
		AT91F_PDC_EnableRx(AT91C_BASE_PDC_SPI0);
		#endif
	}
	else
	{
		/*Enable TX interrupt*/
		AT91F_SPI_EnableIt(AT91C_BASE_SPI0, AT91C_SPI_TXEMPTY);  
	}
 
	/*Enable Transmission*/
	#if SPI_SENDRCV_DIS_W_SETTING == 1
	AT91F_PDC_EnableTx(AT91C_BASE_PDC_SPI);
	#endif
 
	/*Block to wait results*/
	if (xSemaphoreTake(spiPort0.event.xSemaphore, (portTickType) spiPort0.event.xBlockTime ) == pdTRUE)
	{
		/*Empty receiver-register*/
		if (pData->sizeR > 0) {
			AT91F_SPI_GetChar(AT91C_BASE_SPI0);
		}
		errCode = SUCCESS;
	}
	else {
		errCode = ERR_TASK_TIMEOUT;
	}
 
	/*Release mutex*/
	xSemaphoreGive(spiPort0.mutex.xSemaphore);

	return errCode;
}

void SPI_SendCommand(unsigned portCHAR cmd)
{
	//For the SPI0
	tSpiRWdata spiData;
	portCHAR   buffer[10];

	/*Prepare data for SPI transaction (Read and Write)*/
	spiData.device = ChipSelect0; //Use Chipselect 0
	spiData.holdCS = 0;           //Do NOT maintain selected the Chipselect after the transfer
	spiData.clockDivider = 0x2;  //Baud = clock / divider;
	spiData.sizeR = 0;           //Bytes to read
	spiData.sizeW = 1;           //Bytes to write
	spiData.dataR = (unsigned portCHAR *) buffer;   //Place to store data
	spiData.dataW = &cmd;		//Place from where to get data
	spiData.xBlockTime = 300;	//Maximum block time

	SSD1306_SPI_ReadWrite(&spiData);
}

void SPI_SendData(unsigned portCHAR* pData, size_t size)
{
	//For the SPI0
	tSpiRWdata spiData;
	portCHAR   buffer[10];

	/*Prepare data for SPI transaction (Read and Write)*/
	spiData.device = ChipSelect0; //Use Chipselect 0
	spiData.holdCS = 0;           //Do NOT maintain selected the Chipselect after the transfer
	spiData.clockDivider = 0x2;  //Baud = clock / divider;
	spiData.sizeR = 0;           //Bytes to read
	spiData.sizeW = size;           //Bytes to write
	spiData.dataR = (unsigned portCHAR *) buffer;   //Place to store data
	spiData.dataW = pData;		//Place from where to get data
	spiData.xBlockTime = 300;	//Maximum block time

	SSD1306_SPI_ReadWrite(&spiData);
}

/* -------------------------------------------------------------------------------------------- */
eSpiInitError SD_SPI_Initialize(eCSDevice device)
{
	unsigned portLONG ulPeriphA = ((unsigned portLONG) AT91C_PA24_MISO | (unsigned portLONG) AT91C_PA23_MOSI | (unsigned portLONG) AT91C_PA22_SPCK);
	unsigned portLONG ulPeriphB = (unsigned portLONG) 0;        
 
	/*Enable SPI1 PIO (SI, SO, SCK, CS)*/
	switch (device)
	{
		case ChipSelect0: ulPeriphA |= SPI1_IO_CS0A; ulPeriphB |= SPI1_IO_CS0B; break;
		case ChipSelect1: ulPeriphA |= SPI1_IO_CS1A; ulPeriphB |= SPI1_IO_CS1B; break;
		case ChipSelect2: ulPeriphA |= SPI1_IO_CS2A; ulPeriphB |= SPI1_IO_CS2B; break;
		case ChipSelect3: ulPeriphA |= SPI1_IO_CS3A; ulPeriphB |= SPI1_IO_CS3B; break;
		default: return spiInitMaxCs;
	}
	AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, ulPeriphA, ulPeriphB);

	/*Enable SPI Clocks*/
	AT91F_PMC_EnablePeriphClock(AT91C_BASE_PMC, 1 << AT91C_ID_SPI1);

	/*Disable SPI while configuring*/
	AT91F_SPI_Disable(AT91C_BASE_SPI1);

	/*Disable PDC for SPI*/
	AT91F_PDC_DisableRx(AT91C_BASE_PDC_SPI1);
	AT91F_PDC_DisableTx(AT91C_BASE_PDC_SPI1);

	/*Initialize PDC TX and RX*/
	AT91F_PDC_SetTx(AT91C_BASE_PDC_SPI1, 0, 0);
	AT91F_PDC_SetNextTx(AT91C_BASE_PDC_SPI1, 0, 0);
	AT91F_PDC_SetRx(AT91C_BASE_PDC_SPI1, 0, 0);
	AT91F_PDC_SetNextRx(AT91C_BASE_PDC_SPI1, 0, 0);
 
	/*Enable PDC for SPI*/
	AT91F_PDC_EnableRx(AT91C_BASE_PDC_SPI1);
	AT91F_PDC_EnableTx(AT91C_BASE_PDC_SPI1);

	/*Reset the module, necessarily 2 for the SPI state machine*/
	AT91F_SPI_Reset(AT91C_BASE_SPI1); AT91F_SPI_Reset(AT91C_BASE_SPI1);

	/*Enable SPI module*/
	AT91F_SPI_Enable(AT91C_BASE_SPI1);

	/*Configure Master mode comunication with Variable Peripheral Select and Chip select w/o decode*/
	AT91F_SPI_CfgMode(AT91C_BASE_SPI1, AT91C_SPI_MSTR | AT91C_SPI_MODFDIS | AT91C_SPI_PS_FIXED);

	/*Configure and enable interrupts*/
	AT91F_SPI_DisableIt(AT91C_BASE_SPI1, AT91C_SPI_TXEMPTY | AT91C_SPI_RXBUFF);
	
	//From at91lib\peripherals\irq\aic.c,
	//http://www.keil.com/dd/docs/arm/atmel/sam7s/at91sam7s64.h
	//AT91C_AIC_SRCTYPE_INT_LEVEL_SENSITIVE is AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL
	
	AT91F_AIC_ConfigureIt(/*AT91C_BASE_AIC,*/ AT91C_ID_SPI1, AT91C_AIC_PRIOR_LOWEST + 1,
		/*AT91C_AIC_SRCTYPE_INT_LEVEL_SENSITIVE,*/AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, ( void (*)(void) ) vSpiISREntry1);
	AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_SPI1);
 
	/*Create and initialize semaphores*/
	if (!spiPort1.dev_1_Enabled && !spiPort1.dev_2_Enabled && !spiPort1.dev_3_Enabled && !spiPort1.dev_4_Enabled)
	{
		vSemaphoreCreateBinary(spiPort1.mutex.xSemaphore);
		vSemaphoreCreateBinary(spiPort1.event.xSemaphore);
		spiPort1.mutex.xBlockTime = INITIAL_OS_BLOCK_TIME_MAX;
	}
 
	/*Mark module as enabled, store config data*/
	switch (device)
	{
		case ChipSelect0: spiPort1.dev_1_Enabled = 1; break;
		case ChipSelect1: spiPort1.dev_2_Enabled = 1; break;
		case ChipSelect2: spiPort1.dev_3_Enabled = 1; break;
		case ChipSelect3: spiPort1.dev_4_Enabled = 1; break;
		default: return spiInitMaxCs;
	}

	return   spiInitSuccess;
}

void SD_SPI_Close(eCSDevice device)
{
}

unsigned portCHAR SD_SPI_ReadWrite(pSpiRWdata pData)
{
}
